package com.star.learn.excel.component;

import com.rabbitmq.client.Channel;
import com.star.learn.excel.domain.Student;
import com.star.learn.excel.mapper.StudentMapper;
import com.star.learn.excel.service.RedisMetadataService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

@Slf4j
@Component
public class StudentConsumer {

    @Resource
    private StudentMapper studentMapper;

    @Resource
    private RedisMetadataService redisMetadataService;

    // 使用 manualAckContainerFactory 确保手动确认
    @RabbitListener(queues = "${spring.rabbitmq.queue.name}", containerFactory = "manualAckContainerFactory")
    public void handleMessage(Message message, Channel channel) throws Exception {
        long tag = message.getMessageProperties().getDeliveryTag();
        Map<String, Object> headers = message.getMessageProperties().getHeaders();
        String fileId = (String) headers.get("fileId");
        String sheetName = (String) headers.get("sheetName");
        Integer batchNum = (Integer) headers.get("batchNum");
        String correlationId = message.getMessageProperties().getCorrelationId();

        log.debug("开始消费触发消息. FileId: {}, Sheet: {}, Batch: {}, CorrelationId: {}",
                fileId, sheetName, batchNum, correlationId);

        List<Student> students = null;
        try {
            // 1. 从 Redis 获取实际批次数据
            students = redisMetadataService.getBatchData(fileId, sheetName, batchNum);

            if (students == null || students.isEmpty()) {
                // 数据在 Redis 中不存在、已过期或为空
                log.warn("文件 (FileId: {}), Sheet {}, Batch {}: 消费触发消息，但 Redis 中未找到数据或数据为空.",
                        fileId, sheetName, batchNum);

                // **优化逻辑开始：检查批次元数据状态**
                Map<String, String> batchMeta = redisMetadataService.getBatchMetadata(fileId, sheetName, batchNum);

                if (batchMeta != null && "COMPLETED".equals(batchMeta.get("status"))) {
                    // 如果元数据状态已经是 COMPLETED，说明该批次已经成功处理过 (数据已入库并从 Redis 删除)
                    log.info("文件 (FileId: {}), Sheet {}, Batch {}: Redis 元数据已显示 COMPLETED，数据缺失是正常情况，直接 ACK.",
                            fileId, sheetName, batchNum);
                    channel.basicAck(tag, false); // 正常 ACK 消息
                } else {
                    // 如果元数据状态不是 COMPLETED，且 Redis 数据缺失，说明数据可能丢失或过期，且批次处理未完成。
                    // 这是一个异常情况，将消息 NACK 到死信队列。
                    log.error("文件 (FileId: {}), Sheet {}, Batch {}: Redis 数据缺失且元数据状态不是 COMPLETED (当前状态: {}), 将 NACK 进入死信队列.",
                            fileId, sheetName, batchNum, batchMeta != null ? batchMeta.get("status") : "元数据不存在");
                    // NACK，不重回队列，触发 RabbitMQ 重试 (如果还有剩余次数) 或最终进入 DLQ
                    channel.basicNack(tag, false, false);
                    // 注意：这里 NACK 后，如果配置了重试，RabbitMQ 会自动重试。
                    // 重试耗尽后，RejectAndDontRequeueRecoverer 会再次 NACK(false, false)，消息最终进入 DLQ。
                    // 即使重试，Redis 数据也找不回来了，最终都会去 DLQ。
                }
                // **优化逻辑结束**
            } else {
                // 数据成功从 Redis 获取
                // 2. 写入数据库 (利用 ON DUPLICATE KEY UPDATE 实现幂等性)
                try {
                    studentMapper.insertBatch(students);
                    log.info("文件 (FileId: {}), Sheet {}, Batch {}: 成功将 {} 条数据写入数据库.",
                            fileId, sheetName, batchNum, students.size());

                    // 3. 从 Redis 删除已成功处理的批次数据
                    redisMetadataService.deleteBatchData(fileId, sheetName, batchNum);
                    log.debug("文件 (FileId: {}), Sheet {}, Batch {}: 已从 Redis 删除批次数据.",
                            fileId, sheetName, batchNum);

                    // 4. ACK 消息 (在数据库写入和 Redis 数据删除成功后)
                    channel.basicAck(tag, false);
                    log.info("文件 (FileId: {}), Sheet {}, Batch {}: 成功 ACK 消息.",
                            fileId, sheetName, batchNum);

                    // 5. 更新当前批次状态为 COMPLETED 并累加总记录数
                    int actualBatchSize = students.size(); // 使用从 Redis 读取到的实际数据大小
                    // 注意：即使 updateBatchMetadata 失败，此处已 ACK 消息且 DB 写入成功，不会重试此消息。
                    // 元数据更新 需要通过监控发现。
                    redisMetadataService.updateBatchMetadata(fileId, sheetName, batchNum, actualBatchSize, "COMPLETED");
                    redisMetadataService.incrementTotalCount(fileId, actualBatchSize);

                    // 6. 检查是否所有批次都完成
                    // 注意：即使 checkAndMarkFileComplete 失败，也不会影响当前批次的成功处理和 ACK。
                    redisMetadataService.checkAndMarkFileComplete(fileId);

                } catch (Exception dbWriteOrRedisDeleteException) {
                    // 捕获写入 DB 或 删除 Redis 数据 时的异常
                    log.error("文件 (FileId: {}), Sheet {}, Batch {}: 写入数据库或删除Redis数据失败.",
                            fileId, sheetName, batchNum, dbWriteOrRedisDeleteException);
                    // NACK 消息，触发重试或进入 DLQ
                    channel.basicNack(tag, false, false);
                    // 注意：如果在 updateBatchMetadata 或 incrementTotalCount 发生异常，消息已经被 ACK 了，不会重试。
                    // 这是手动 ACK 模式的一种权衡。需要在这些地方加强异常处理和日志记录，或考虑更复杂的事务保障。
                    // 但对于核心的 DB 写入和 Redis 数据删除，失败会 NACK。
                }
            }

        } catch (Exception initialRedisGetException) {
            // 捕获首次从 Redis 获取数据时的异常 (例如 Redis 连接问题)
            log.error("文件 (FileId: {}), Sheet {}, Batch {}: 从 Redis 获取数据失败.",
                    fileId, sheetName, batchNum, initialRedisGetException);
            // NACK 消息，触发重试或进入 DLQ
            channel.basicNack(tag, false, false);
        }
    }
}